//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Data.Common;
using NF = System;

namespace Microsoft.Research.DataLayer
{
    /// <summary>
    /// Represents an object that has a strong name (e.g. service or namespace)
    /// </summary>
    /// <typeparam name="T">Type of object</typeparam>
    public abstract class NamedObject<T> : BasicObject, INamedObject
        where T : NamedObject<T>
    {
        /// <summary>
        /// Object name
        /// </summary>
        protected string ObjName;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="type">Type of object</param>
        /// <param name="c">Valid connection to the storage backend</param>
        protected NamedObject(Connection.Storage type, Connection c)
            : base(type, c)
        {
        }

        /// <summary>
        /// Name of the object
        /// </summary>
        public string Name
        {
            get { return ObjName == null ? null : ObjName.Trim(); }
            set
            {
                ObjName = value;

                if (Conn.AutoSave)
                    Save();
            }
        }

        /// <summary>
        /// Creates a new instance of the object
        /// </summary>
        /// <param name="c">Valid connection to the storage backend</param>
        /// <returns>Reference to new instance of the object</returns>
        protected static T New(Connection c)
        {
            // Create an object instance
            ConstructorInfo ci = typeof(T).GetConstructor(new System.Type[] { typeof(Connection) });
            T newObj = (T)ci.Invoke(new object[] { c });
            return newObj;
        }


        /// <summary>
        /// Creates a new instance of the object from the data fetch from the backend
        /// </summary>
        /// <param name="reader">DbDataReader that holds the object data</param>
        /// <param name="c">Valid connection to the data store</param>
        /// <returns>Instance (in-memory) of the object</returns>
        protected static T CreateFromDataStore(DbDataReader reader, Connection c)
        {
            T newObj = New(c);

            LoadFromDataStore(newObj, reader, c);

            return newObj;
        }

        /// <summary>
        /// Loads an object from the data store by name
        /// </summary>
        /// <param name="name">Name of the object to find</param>
        /// <param name="c">Valid connection to the data store</param>
        /// <param name="creator">Creation function to build the object from data</param>
        /// <returns>Object being sought</returns>
        protected static T Load(string name, Connection c, Connection.ObjectCreator creator)
        {
            List<Parameter> mydata = new List<Parameter>();
            mydata.Add(new Parameter("Name", Parameter.ParamType.String, Parameter.ParamDirection.In, name));

            Parameter dbid = new Parameter("ID", Parameter.ParamType.Guid, Parameter.ParamDirection.Result, Guid.Empty);
            mydata.Add(dbid);

            PipelineParameter<T> newObj = new PipelineParameter<T>(creator);
            mydata.Add(newObj);

            c.Object(Connection.TypeStoreMap[typeof(T)], Connection.Action.Get, ref mydata);

            return newObj.Value;
        }

        /// <summary>
        /// Deserializes the objects from storage format into memory
        /// </summary>
        /// <param name="newObj"></param>
        /// <param name="reader"></param>
        /// <param name="c"></param>
        protected static void LoadFromDataStore(T newObj, DbDataReader reader, Connection c)
        {
            Guid id = reader.GetGuid(reader.GetOrdinal("ID"));
            string name = reader.GetString(reader.GetOrdinal("Name"));

            newObj.ObjID = id;
            newObj.ObjName = name;
            newObj.isNew = false;
            newObj.Created = new EntryChange(
                reader.GetString(reader.GetOrdinal("CreatedBy")),
                reader.GetDateTime(reader.GetOrdinal("CreatedDate")));
            newObj.Updated = new EntryChange(
                reader.GetString(reader.GetOrdinal("UpdatedBy")),
                reader.GetDateTime(reader.GetOrdinal("UpdatedDate")));
        }

        /// <summary>
        /// Creates a new object
        /// </summary>
        /// <param name="name">Name of the object</param>
        /// <param name="c">Valid connection to the data store</param>
        /// <returns>In-memory version of the object</returns>
        protected static T Create(string name, Connection c)
        {
            T newObj = New(c);
            newObj.ObjID = Guid.Empty;
            newObj.ObjName = name;
            newObj.isNew = true;

            if (c.AutoSave)
                newObj.Save();

            return newObj;
        }

        /// <summary>
        /// Checks if an object with the input name exists in the data store
        /// </summary>
        /// <param name="name">true if object exists</param>
        /// <param name="conn">Valid connection to the data store</param>
        /// <returns>true if object exists, false otherwise</returns>
        public static bool Exists(string name, Connection conn)
        {
            return ExistsInternal(typeof(T), name, conn);
        }

        /// <summary>
        /// Checks if the object <u>still</u> exists in the data store
        /// </summary>
        /// <returns>true if object exists, false otherwise</returns>
        public bool Exists()
        {
            return ExistsInternal(typeof(T), this.ObjID, Conn);
        }

        /// <summary>
        /// Helper function to execute Exists() functionality. Uses generic activation
        /// mechanisms to load the object and checks existance.
        /// </summary>
        /// <param name="myType">Type of object being searched</param>
        /// <param name="key">Key to search the object by</param>
        /// <param name="conn">Valid connection to the data store</param>
        /// <returns>true if object exists, false otherwise</returns>
        /// <seealso cref="Exists()"/>
        public static bool ExistsInternal(NF.Type myType, object key, Connection conn)
        {
            try
            {
                try
                {
                    // Create an object instance
                    MethodInfo mi = typeof(T).GetMethod("Load",
                        BindingFlags.Static | BindingFlags.Public, null,
                        new System.Type[] { key.GetType(), typeof(Connection) }, null);
                    T objInStore = (T)mi.Invoke(null, new object[] { key, conn });
                    return true;
                }
                catch (TargetInvocationException e)
                {
                    Exception err = e.InnerException;

                    if (err is RecordNotFoundException)
                        return false;
                    else
                    {
                        throw new InvalidOperationException("Problem with the object model", e);
                    }
                }
            }
            catch (RecordNotFoundException)
            {
                return false;
            }
        }

        /// <summary>
        /// Reads a fresh copy of the objects data from the backend
        /// </summary>
        public override void Refresh()
        {
            List<Parameter> mydata = new List<Parameter>();
            mydata.Add(new Parameter("ID", Parameter.ParamType.Guid, Parameter.ParamDirection.In, this.ObjID));

            Parameter dbname = new Parameter("Name", Parameter.ParamType.String, Parameter.ParamDirection.Result, null);
            mydata.Add(dbname);

            Conn.Object(Connection.TypeStoreMap[typeof(T)], Connection.Action.GetByID, ref mydata);

            this.ObjName = (string)dbname.Value;
            this.isNew = false;
        }

        /// <summary>
        /// Saves the object (new or updates) into storage backend
        /// </summary>
        /// <param name="mydata">Object specific serialized data</param>
        protected override void Save(List<Parameter> mydata)
        {
            mydata.Add(new Parameter("Name", Parameter.ParamType.String, Parameter.ParamDirection.In, this.ObjName));
            base.Save(mydata);
        }

        /// <summary>
        /// Creates a copy of the object in an alternate store
        /// </summary>
        /// <param name="mydata"></param>
        /// <param name="target"></param>
        /// <param name="pipelineCreator"></param>
        /// <returns></returns>
        protected override IObject SaveTo(List<Parameter> mydata, Connection target, Parameter pipelineCreator)
        {
            mydata.Add(new Parameter("Name", Parameter.ParamType.String, Parameter.ParamDirection.In, this.ObjName));
            return base.SaveTo(mydata, target, pipelineCreator);
        }

        /// <summary>
        /// Saves the object (new or updates) into storage backend
        /// </summary>
        public override void Save()
        {
            List<Parameter> mydata = new List<Parameter>();
            Save(mydata);
        }
    }
}
